#ifndef WX_VIEWSUPT_H
#define WX_VIEWSUPT_H

#include <wx/spinctrl.h>
#include <wx/caret.h>

// avoid exporting too much stuff
namespace Viewers {
    // common to all viewers:
    //   - track in MainFrame::popups
    //   - wxID_CLOSE button closes window
    //   - AutoUpdate checkbox toggles calling Update() every screen refresh
    class Viewer : public wxDialog
    {
    public:
	void CloseDlg(wxCloseEvent &ev);
	Viewer(const wxChar *name);
	virtual ~Viewer() {}
	virtual void Update() = 0;
	bool auto_update;

	// A lot of viewers have GUI elements to set parameters.  Almost all
	// of them just read back the value and update the display.  This
	// event handler does that with validators.
	void ActiveCtrl(wxCommandEvent &ev);
	void ActiveCtrlScr(wxScrollEvent &ev) { ActiveCtrl(ev); }
	void ActiveCtrlSpin(wxSpinEvent &ev) { ActiveCtrl(ev); }

    protected:
	const wxChar *dname;
	void SetAutoUpdate(wxCommandEvent &ev) { auto_update = ev.IsChecked(); }

	DECLARE_EVENT_TABLE()
    };

    // on errors, abort program
#define baddialog() do { \
    wxLogError(_("Unable to load dialog %s from resources"), dname); \
    wxGetApp().frame->Close(true); \
    return; \
} while(0)

    // widgets to use with auto validator
#define getvfld(sv, n, t, v) do { \
    t *_w = sv XRCCTRL(*this, n, t); \
    if(!_w) \
	baddialog(); \
    _w->SetValidator(v); \
} while(0)
#define getradio(sv, n, var, val) \
    getvfld(sv, n, wxRadioButton, wxBoolIntValidator(&var, val))
#define getslider(sv, n, var) \
    getvfld(sv, n, wxSlider, wxGenericValidator(&var))
#define getspin(sv, n, var) \
    getvfld(sv, n, wxSpinCtrl, wxGenericValidator(&var))

#define LoadXRCViewer(t) do { \
    wxDialog *d = new Viewers::t##Viewer; \
    if(d) \
	d->Show(); \
} while(0)

    // a list box with no horizontal scrollbar and a funky vertical scrollbar:
    // range = 1 - 500
    //   but/pagesz = # of lines shown/# of lines shown - 1
    // 1-100 = normal
    // 101-200 = 10x semi-stationary
    // 201-300 = stationary @ center
    // 301-400 = 10x semi-stationary
    // 401-500 = normal

    // note that since listboxes' size is impossible to control correctly (at
    // least with wxGTK), this uses a textctrl to display the items.

    class DisList : public wxPanel
    {
    public:
	DisList();
	// called after init to create subcontrols and size panel
	void Refit(int cols);
	void MoveSB();
	void MoveView(wxScrollEvent &ev);
    private:
	void RefillNeeded();
    public:
	// called by parent's refill handler or any other time strings have
	// changed
	void Refill();
    private:
	void Resize(wxSizeEvent &ev);
	void SetSel();

    public:
	// make addr visible and then select it
	void SetSel(u32 addr);
	void UnSel() { issel = false; }

	// currently visible lines
	int nlines;
	// at least nlines strings to display
	wxArrayString strings;
	// and their starting addrs (mostly for scrollbar)
	wxArrayInt addrs;
	// how far back to scroll for single line
	int back_size;
	// address of top line
	u32 topaddr;
	// max address for scrollbar
	u32 maxaddr;
    protected:
	// assigned to textctrl to avoid mouse input
	void MouseEvent(wxMouseEvent &ev) {}
	// the subwidgets
	wxTextCtrl tc;
	wxScrollBar sb;
	// cached computed line tc size
	int lineheight, extraheight;
	// need to know if tc/sb have been Create()d yet
	bool didinit;
	// selection info
	u32 seladdr;
	bool issel;

	DECLARE_DYNAMIC_CLASS() // for xrc
        DECLARE_EVENT_TABLE()
    };

    BEGIN_DECLARE_EVENT_TYPES()
	// event generated when fewer lines available than needed
        DECLARE_LOCAL_EVENT_TYPE(EVT_REFILL_NEEDED, 0)
    END_DECLARE_EVENT_TYPES()

    // a hex editor with a funky scrollbar like above

    // since it's impossible to exercise this much control over a text
    // control, it uses a panel into which text is drawn.
    // Maybe some day the above will be changed to do that as well, since
    // it allows better mouse control as well.

    class MemView : public wxPanel
    {
    public:
	MemView();
	// called after init to create subcontrols and size panel
	void Refit();
	void MoveSB();
	void MoveView(wxScrollEvent &ev);
    private:
	void RefillNeeded();
    public:
	// called by parent's refill handler or any other time strings have
	// changed
	void Refill();
    private:
	void Refill(wxDC &dc);
	void RepaintEv(wxPaintEvent &ev);
	void Repaint();
	void Resize(wxSizeEvent &ev);

    public:
	// make addr visible
	void Show(u32 addr, bool force_update = false);

	// current selection, or topaddr if none
	u32 GetAddr();
	// currently visible lines
	int nlines;
	// at least nlines * 4 words to display
	wxArrayInt words;
	// address of top line
	u32 topaddr;
	// max address for scrollbar
	u32 maxaddr;
	// bytes per word == (1 << fmt)
	int fmt;
	// after write, these contain write addr and val
	u32 writeaddr, writeval;
	// when selection is made, this widget is updated w/ addr
	wxControl *addrlab;
    protected:
	// easier than checking maxaddr
	int addrlen;

	void MouseEvent(wxMouseEvent &ev);
	void KeyEvent(wxKeyEvent &ev);
	// the subwidgets
	wxPanel disp;
	wxScrollBar sb;
	wxCaret *caret;
	// cached text size
	int charheight, charwidth;
	// need to know if tc/sb have been Create()d yet
	bool didinit;
	// selection info
	int selnib, seladdr;
	bool isasc;
	void ShowCaret();

	DECLARE_DYNAMIC_CLASS() // for xrc
        DECLARE_EVENT_TABLE()
    };

    BEGIN_DECLARE_EVENT_TYPES()
	// event generated when write occurs
	// check writeaddr/writeval/fmt
        DECLARE_LOCAL_EVENT_TYPE(EVT_WRITEVAL, 0)
    END_DECLARE_EVENT_TYPES()

    // Display a color in a square, with the RGB value to its right.

    // this is too hard to integrate into xrc (impossible to initialize
    // correctly) so no accomodations are made for this

    class ColorView : public wxControl
    {
    public:
	ColorView(wxWindow *parent, wxWindowID id);
	void SetRGB(int r, int g, int b);
	void GetRGB(int &_r, int &_g, int &_b) { _r = r; _g = g; _b = b; }

    protected:
	int r, g, b;
	wxPanel *cp;
	wxStaticText *rt, *gt, *bt;
    };
#define unkctrl(n, v) do { \
	if(!wxXmlResource::Get()->AttachUnknownControl(wxT(n), v, this)) \
	    baddialog(); \
} while(0)
#define colorctrl(v, n) do { \
    v = new ColorView(this, XRCID(n)); \
    if(!v) \
	baddialog(); \
    unkctrl(n, v); \
} while(0)

    // Display a small bitmap in jumbopixel style.  If a pixel is selected, it
    // is highlighted with a border.  For wxvbam, no event is generated.
    // Instead, a ColorView can be assigned to it, and on selection, that
    // widget will be updated to the selected color.
    // The whole class can also be derived to add more functionality to the
    // button click.

    // It must be intialized in 3 phases: 2-phase xrc-style (new + Create()),
    // and then InitBMP()

    class PixView : public wxPanel
    {
    public:
	PixView() : wxPanel(), bm(0) {}
	bool InitBMP(int w = 8, int h = 8, ColorView *cv = NULL);
	// stride is in pixels
	// format is rgb24 (aka wxImage format)
	// x/y is added to data and returned coords
	// if data == NULL, bitmap will be reset to default (all-black)
	virtual void SetData(const unsigned char *data, int stride, int x = 0,
			     int y = 0);
	// desel if out of displayed range
	void SetSel(int x, int y, bool dsel_cview_update = true);
	// -1, -1 = no sel
	void GetSel(int &x, int &y) {
	    x = selx < 0 ? -1 : ox + selx;
	    y = sely < 0 ? -1 : oy + sely;
	}
	ColorView *cview;
    protected:
	wxImage im;
	wxBitmap *bm;
	void Redraw(wxPaintEvent &ev);
	virtual void SelPoint(wxMouseEvent &ev);
	int ox, oy, selx, sely;

	DECLARE_EVENT_TABLE()
	DECLARE_DYNAMIC_CLASS()
    };
#define pixview(v, n, w, h, cv) do { \
    v = XRCCTRL(*this, n, PixView); \
    if(!v) \
	baddialog(); \
    v->InitBMP(w, h, cv); \
} while(0)

    // a graphics viewer panel; expected to be inside of a wxScrollWindow
    class GfxPanel : public wxPanel
    {
    public:
	GfxPanel() : wxPanel(), bm(0), selx(-1), sely(-1) {}
	int bmw, bmh;
	wxBitmap *bm;
	wxImage *im;
	PixView *pv;

    protected:
	void DrawBitmap(wxPaintEvent &ev);
	void Click(wxMouseEvent &ev);
	void MouseMove(wxMouseEvent &ev);
	void DoSel(wxMouseEvent &ev, bool force = false);
    private:
	int selx, sely;

	DECLARE_DYNAMIC_CLASS()
	DECLARE_EVENT_TABLE()
    };


    BEGIN_DECLARE_EVENT_TYPES()
	// event generated on mouse click
	// generates wxMouseEvent with coords adjusted to original bitmap
	// size regardless of scaling
        DECLARE_LOCAL_EVENT_TYPE(EVT_COMMAND_GFX_CLICK, 0)
    END_DECLARE_EVENT_TYPES()
#define EVT_GFX_CLICK(id, fun) \
    DECLARE_EVENT_TABLE_ENTRY(EVT_COMMAND_GFX_CLICK, id, wxID_ANY, wxMouseEventHandler(fun), NULL),

    // like Viewer, common stuff to all gfx viewers
    // this is what actually manages the GfxPanel
    class GfxViewer : public Viewer
    {
    public:
	GfxViewer(const wxChar *dname, int maxw, int maxh);
	void ChangeBMP();
	void BMPSize(int w, int h);
    protected:
	void StretchTog(wxCommandEvent &ev);
	void RefreshEv(wxCommandEvent &ev);
	void SaveBMP(wxCommandEvent &ev);
	wxImage image;
	GfxPanel *gv;
    private:
	static wxString bmp_save_dir;
	wxScrolledWindow *gvs;
	wxCheckBox *str;

	DECLARE_EVENT_TABLE()
    };

    // if the jumbopixel view is all there is, maybe send a GFX_CLICK..
    class PixViewEvt : public PixView
    {
	// generates a GFX_CLICK if a point is selected
	void SetData(const unsigned char *data, int stride, int x = 0,
		     int y = 0);
    protected:
	// always generates a GFX_CLICK
	void SelPoint(wxMouseEvent &ev);
	void click();
	DECLARE_DYNAMIC_CLASS()
    };

    // a display-only checkbox which does not look like it's disabled
    class DispCheckBox : public wxCheckBox
    {
    public:
	bool AcceptsFocus() { return false; }
	void MouseEvent(wxMouseEvent &ev) {}
	DECLARE_EVENT_TABLE()
	DECLARE_DYNAMIC_CLASS()
    };

    // standard widgets in graphical viewers

}
#endif /* WX_VIEWSUPT_H */
